/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/netdevice.h>

#include "mrp.h"
#include "mrp_map.h"
#include "mrp_private.h"
#include "mrp_port.h"
#include "mrp_mrpdu.h"
#include "mrp_timer.h"
#include "mrp_attribute.h"
#include "mrp_leave_all.h"
#include "mrp_periodic_transmission.h"
#include "mrp_message_processing_thread.h"
#include "../include/mrp_attribute_properties.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Philip Foulkes <philip.foulkes@gmail.com>");
MODULE_DESCRIPTION("Multiple Registration Protocol");

// ========================================================
struct indication_ops
{
    struct list_head ops_list;
    mrp_mad_join_indication mrp_mad_join_indication;
    mrp_mad_leave_indication mrp_mad_leave_indication;
};

static int call_mrp_mad_join_indications(struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type, int new)
{
    int ret;
    struct indication_ops * ops;
    
    list_for_each_entry (ops, &mrp->inidcation_ops_list, ops_list)
    {
        if ((ret = ops->mrp_mad_join_indication(mrp, mrp_attribute_type, attribute_value, four_packed_type, new)))
            return ret;
    }
    
    return 0;
}

static int call_mrp_mad_leave_indications(struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type)
{
    int ret;
    struct indication_ops * ops;
    
    list_for_each_entry (ops, &mrp->inidcation_ops_list, ops_list)
    {
        if ((ret = ops->mrp_mad_leave_indication(mrp, mrp_attribute_type, attribute_value, four_packed_type)))
            return ret;
    }
    
    return 0;
}
// ========================================================

static void mrp_jointimer_function (unsigned long data)
{
    struct mrp * mrp;
    mrp = (struct mrp *) data;
    mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_JOINTIMER_EXPIRE, NULL, NULL);
}

static void * mrp_attribute_iterator_get (struct mrp_iterator * mrp_iterator, void * my_object)
{
    struct mrp_attribute_properties * mrp_attribute_properties;
    struct mrp_attribute * attribute;
    int attribute_type;
    struct mrp * mrp;

    mrp = (struct mrp *)my_object;
    attribute = list_entry (mrp->mrp_iterator_list_head, struct mrp_attribute, list);
    attribute_type = mrp_attribute_get_attribute_type (attribute);
    
    if (!(mrp_attribute_properties = mrp_attribute_properties_create_and_init(attribute_type,
        mrp_attribute_get_attribute_value(attribute), mrp->mrp_ops->mrp_get_attribute_length(attribute_type),
        mrp_attribute_get_four_packed_type(attribute), 0)))
        return NULL;

    return mrp_attribute_properties;
}

static void mrp_attribute_iterator_next (struct mrp_iterator * mrp_iterator, void * my_object)
{
    struct mrp * mrp;
    mrp = (struct mrp *)my_object;
    mrp->mrp_iterator_list_head = mrp->mrp_iterator_list_head->next;
}

static int mrp_attribute_iterator_at_end (struct mrp_iterator * mrp_iterator, void * my_object)
{
    struct mrp * mrp;
    mrp = (struct mrp *)my_object;
    return mrp->mrp_iterator_list_head->next == mrp->attributes.next;
}

static struct mrp_iterator_ops iterator_ops = {
    .mrp_iterator_get_internal 		= mrp_attribute_iterator_get,
    .mrp_iterator_next_internal 	= mrp_attribute_iterator_next,
    .mrp_iterator_at_end_internal 	= mrp_attribute_iterator_at_end,
};

struct mrp * mrp_create_and_init (const u8 application_address [6], u16 ether_type, const char * port_name, struct mrp_ops * mrp_ops, int protocol_version, int use_attribute_list_length)
{
    struct mrp * mrp = NULL;

    if ( 	! (mrp = kzalloc (sizeof(struct mrp), GFP_KERNEL))
        || 	! mrp_init (mrp, application_address, ether_type, port_name, mrp_ops, protocol_version, use_attribute_list_length))
    {
        mrp_destroy (&mrp);
        return NULL;
    }

    return mrp;
}
EXPORT_SYMBOL(mrp_create_and_init);

int mrp_init (struct mrp * mrp, const u8 application_address [6], u16 ether_type, const char * port_name, struct mrp_ops * mrp_ops, int protocol_version, int use_attribute_list_length)
{
    int ret;
    
    memset (mrp, 0, sizeof (struct mrp));

    if (    ! mrp_ops
        ||  ! mrp_ops->mrp_mad_join_indication
        ||  ! mrp_ops->mrp_mad_leave_indication
        ||  ! mrp_ops->mrp_get_attribute_value_compare
        ||  ! mrp_ops->mrp_get_attribute_length
        ||  ! mrp_ops->mrp_increment_first_value
        ||  ! mrp_ops->mrp_does_need_four_packed_events )
    {
        return -EINVAL;
    }

    if (    ! (mrp->mrp_leave_all = mrp_leave_all_create_and_init ())
        ||  ! (mrp->mrp_periodic_transmission = mrp_periodic_transmission_create_and_init ())
        ||  ! (mrp->mrp_port = mrp_port_create_and_init (application_address, ether_type, port_name))
        ||  ! (mrp->mrp_message_processing_thread = mrp_message_processing_thread_get_instance ())
        ||  ! (mrp->mrp_map = mrp_map_create_and_init()))
    {
        return -ENOMEM;
    }
    
    INIT_LIST_HEAD(&mrp->inidcation_ops_list);
    
    if (    (ret = mrp_iterator_init(&mrp->mrp_iterator, mrp, &iterator_ops))
        ||  (ret = mrp_add_indication_functions(mrp, mrp_ops->mrp_mad_join_indication, mrp_ops->mrp_mad_leave_indication)))
    {
        return ret;
    }
    
    mrp->mrp_ops = mrp_ops;
    mrp->protocol_version = protocol_version;
    mrp->use_attribute_list_length = use_attribute_list_length;
    INIT_LIST_HEAD(&mrp->attributes);
    INIT_LIST_HEAD(&mrp->request_opportunity_list);
    INIT_LIST_HEAD(&mrp->map_list);
    setup_timer (&mrp->jointimer, mrp_jointimer_function, (unsigned long) mrp);

    mrp_port_set_mrp_application (mrp->mrp_port, mrp);
    mrp_periodic_transmission_set_mrp (mrp->mrp_periodic_transmission, mrp);
    mrp_leave_all_set_mrp (mrp->mrp_leave_all, mrp);
    mrp_map_set_mrp(mrp->mrp_map, mrp);

    mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_PERIODIC_TRANSMISSION_BEGIN, mrp->mrp_periodic_transmission, NULL);
    mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_LEAVE_ALL_BEGIN, mrp->mrp_leave_all, NULL);

    mrp_port_start_packet_reception (mrp->mrp_port);

    return 0;
}
EXPORT_SYMBOL(mrp_init);

void mrp_destroy (struct mrp ** mrp)
{
    struct mrp * m;

    m = *mrp;

    if (m)
    {
        mrp_clean_up (m);
        kfree (m);
        *mrp = NULL;
    }
}
EXPORT_SYMBOL(mrp_destroy);

void mrp_clean_up (struct mrp * mrp)
{
    struct mrp_attribute * mrp_attribute;
    struct mrp_attribute * next;
    struct indication_ops * indication_ops;
    struct indication_ops * indication_ops_next;
    
    mrp_port_stop_packet_reception(mrp->mrp_port);
    del_timer_sync (&mrp->jointimer);
    mrp_leave_all_stop_leave_timer(mrp->mrp_leave_all);
    mrp_periodic_transmission_stop_timer(mrp->mrp_periodic_transmission);
    
    mrp_port_destroy (&mrp->mrp_port);
    mrp_leave_all_destroy (&mrp->mrp_leave_all);
    mrp_periodic_transmission_destroy (&mrp->mrp_periodic_transmission);
    mrp_message_processing_thread_release_instance();
    mrp->mrp_message_processing_thread = NULL;
    
    list_for_each_entry_safe (mrp_attribute, next, &mrp->attributes, list)
    {
        list_del (&mrp_attribute->list);
        mrp_attribute_destroy (&mrp_attribute);
    }
    
    list_for_each_entry_safe (indication_ops, indication_ops_next, &mrp->inidcation_ops_list, ops_list)
    {
        list_del(&indication_ops->ops_list);
        kfree(indication_ops);
    }
    
    mrp_map_destroy(&mrp->mrp_map);
    mrp_iterator_cleanup (&mrp->mrp_iterator);
}
EXPORT_SYMBOL(mrp_clean_up);

int mrp_add_indication_functions(struct mrp * mrp, mrp_mad_join_indication mrp_mad_join_indication, mrp_mad_leave_indication mrp_mad_leave_indication)
{
    struct indication_ops * ops;
    
    if(!(ops = kzalloc(sizeof(struct indication_ops), GFP_KERNEL)))
        return -ENOMEM;
    
    ops->mrp_mad_join_indication = mrp_mad_join_indication;
    ops->mrp_mad_leave_indication = mrp_mad_leave_indication;
    
    list_add_tail (&ops->ops_list, &mrp->inidcation_ops_list);
    
    return 0;
}

struct mrp_map * mrp_get_mrp_map(struct mrp * mrp)
{
    return mrp->mrp_map;
}

int mrp_mad_join_request_notify  (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type, int new)
{
    struct mrp_attribute_properties * mrp_attribute_properties;

    if ((mrp_attribute_properties = mrp_attribute_properties_create_and_init (mrp_attribute_type, attribute_value, mrp->mrp_ops->mrp_get_attribute_length(mrp_attribute_type), four_packed_type, new)))
        return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_MAD_JOIN_REQUEST, mrp_attribute_properties, (void (*)(void **))mrp_attribute_properties_destroy);

    return -ENOMEM;
}
EXPORT_SYMBOL(mrp_mad_join_request_notify);

int mrp_mad_leave_request_notify (struct mrp * mrp, int mrp_attribute_type, void * attribute_value)
{
    struct mrp_attribute_properties * mrp_attribute_properties;

    if ((mrp_attribute_properties = mrp_attribute_properties_create_and_init (mrp_attribute_type, attribute_value, mrp->mrp_ops->mrp_get_attribute_length(mrp_attribute_type), 0, 0)))
        return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_MAD_LEAVE_REQUEST, mrp_attribute_properties, (void (*)(void **))mrp_attribute_properties_destroy);

    return -ENOMEM;
}
EXPORT_SYMBOL(mrp_mad_leave_request_notify);

int mrp_periodic_enable_notify (struct mrp * mrp)
{
    return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_PERIODIC_ENABLE, mrp->mrp_periodic_transmission, NULL);
}
EXPORT_SYMBOL(mrp_periodic_enable_notify);

int mrp_leave_request_on_all_attributes_notify (struct mrp * mrp)
{
    return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_LEAVE_ALL_ATTRIBUTES, NULL, NULL);
}
EXPORT_SYMBOL(mrp_leave_request_on_all_attributes_notify);

int mrp_periodic_disable_notify (struct mrp * mrp)
{
    return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_PERIODIC_DISABLE, mrp->mrp_periodic_transmission, NULL);
}
EXPORT_SYMBOL(mrp_periodic_disable_notify);

struct mrp_iterator * mrp_get_mrp_attribute_iterator (struct mrp * mrp)
{
    mrp->mrp_iterator_list_head = mrp->attributes.next;
    return &mrp->mrp_iterator;
}
EXPORT_SYMBOL(mrp_get_mrp_attribute_iterator);

int mrp_call_function_on_message_thread (struct mrp * mrp, message_processing_thread_fucntion message_processing_thread_fucntion, void * data)
{
    return mrp_message_processing_thread_call_function (mrp->mrp_message_processing_thread, mrp, message_processing_thread_fucntion, data);
}
EXPORT_SYMBOL(mrp_call_function_on_message_thread);

int mrp_is_address_an_mrp_application_address (u8 address [6])
{
    return memcmp(address, mrp_application_address_mmrp, 5) == 0 && address [5] >= 0x20 && address [5] <= 0x2F ? 1 : 0;
}
EXPORT_SYMBOL(mrp_is_address_an_mrp_application_address);

int mrp_is_address_a_vlan_component_reserved_address (u8 address [6])
{
    u8 start [5] = { 0x01, 0x80, 0xC2, 0x00, 0x00 };
    return memcmp (address, start, 5) == 0 && address [5] <= 0x0F ? 1 : 0;
}
EXPORT_SYMBOL(mrp_is_address_a_vlan_component_reserved_address);

static struct mrp_attribute * mrp_add_and_return_attribute (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type)
{
    struct mrp_attribute * mrp_attribute;
    mrp_attribute_value_compare mrp_attribute_value_compare;

    if ((mrp_attribute_value_compare = mrp->mrp_ops->mrp_get_attribute_value_compare (mrp_attribute_type)))
    {
        list_for_each_entry (mrp_attribute, &mrp->attributes, list)
        {
            if (mrp_attribute_type == mrp_attribute_get_attribute_type (mrp_attribute) && mrp_attribute_value_compare (attribute_value, mrp_attribute_get_attribute_value (mrp_attribute)) == 0)
            {
                return mrp_attribute;
            }
        }

        if ((mrp_attribute = mrp_attribute_create_and_init (
            mrp, mrp_attribute_type, attribute_value,
            mrp->mrp_ops->mrp_get_attribute_length (mrp_attribute_type),
            four_packed_type, mrp_attribute_value_compare )))
        {
            list_add_tail (&mrp_attribute->list, &mrp->attributes);
            return mrp_attribute;
        }
    }

    return NULL;
}

/*static int mrp_add_attribute (struct mrp * mrp, int mrp_attribute_type, void * attribute_value)
{
    return mrp_add_and_return_attribute(mrp, mrp_attribute_type, attribute_value) ? 0 : -ENOMEM;
}*/

static struct mrp_attribute * mrp_remove_and_return_attribute (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, mrp_attribute_value_compare mrp_attribute_value_compare)
{
    struct mrp_attribute * mrp_attribute_cursor;
    struct mrp_attribute * mrp_attribute_next;

    list_for_each_entry_safe (mrp_attribute_cursor, mrp_attribute_next, &mrp->attributes, list)
    {
        if ( 	mrp_attribute_get_attribute_type (mrp_attribute_cursor) == mrp_attribute_type
            && 	mrp_attribute_value_compare (mrp_attribute_get_attribute_value (mrp_attribute_cursor), attribute_value) == 0 )
        {
            list_del (&mrp_attribute_cursor->list);
            return mrp_attribute_cursor;
        }
    }

    return NULL;
}

static int mrp_remove_attribute (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, mrp_attribute_value_compare mrp_attribute_value_compare)
{
    struct mrp_attribute * mrp_attribute_cursor;

    if ((mrp_attribute_cursor = mrp_remove_and_return_attribute(mrp, mrp_attribute_type, attribute_value, mrp_attribute_value_compare)))
    {
        mrp_attribute_destroy (&mrp_attribute_cursor);
        return 0;
    }
    else
        return -ENOMEM;
}

static struct mrp_attribute * mrp_get_attribute (struct mrp * mrp, int mrp_attribute_type, void * attribute_value)
{
    struct mrp_attribute * mrp_attribute_cursor;

    list_for_each_entry (mrp_attribute_cursor, &mrp->attributes, list)
    {
        if ( mrp_attribute_compare (mrp_attribute_cursor, mrp_attribute_type, attribute_value) == 0 )
        {
            return mrp_attribute_cursor;
        }
    }

    return NULL;
}

int mrp_receive_mrpdu_notify (struct mrp * mrp, struct mrp_mrpdu * mrp_mrpdu)
{
    return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_PROCESS_INCOMING_MRPDU, mrp_mrpdu, (void(*)(void**))mrp_mrpdu_destroy);
}

int mrp_perform_request_transmit_opportunity (struct mrp * mrp, struct mrp_state_machine * mrp_state_machine)
{
    struct mrp_state_machine * mrp_state_machine_cursor;

    list_for_each_entry (mrp_state_machine_cursor, &mrp->request_opportunity_list, request_opportunity_list)
        if (mrp_state_machine_cursor == mrp_state_machine)
            return 0;

    list_add_tail (&mrp_state_machine->request_opportunity_list, &mrp->request_opportunity_list);
    return mrp_start_jointimer (mrp);
}

int mrp_leave_all_timer_expire_notify (struct mrp * mrp, struct mrp_leave_all * mrp_leave_all)
{
    return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_LEAVE_ALL_TIMER_EXPIRE, mrp_leave_all, NULL);
}

int mrp_periodic_timer_expire_notify (struct mrp * mrp, struct mrp_periodic_transmission * mrp_periodic_transmission)
{
    return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_PERIODIC_TRANSMISSION_TIMER_EXPIRE, mrp_periodic_transmission, NULL);
}

int mrp_registrar_leave_timer_expire_notify (struct mrp * mrp, struct mrp_attribute * mrp_attribute)
{
    return mrp_message_processing_thread_submit_message (mrp->mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_LEAVE_TIMER_EXPIRE, mrp_attribute, NULL);
}

int mrp_perform_periodic_protocol_action (struct mrp * mrp)
{
    struct mrp_attribute * mrp_attribute;
    list_for_each_entry (mrp_attribute, &mrp->attributes, list)
        mrp_attribute_perform_periodic_protocol_action (mrp_attribute);
    return 0;
}

int mrp_perform_receive_leave_all (struct mrp * mrp, int also_notify_leave_all)
{
    struct mrp_attribute * mrp_attribute;
    int ret = 0;

    if (also_notify_leave_all)
        mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp->mrp_leave_all, MRP_PROTO_EVENT_rLA);

    list_for_each_entry (mrp_attribute, &mrp->attributes, list)
        if (ret |= mrp_attribute_perform_receive_leave_all (mrp_attribute))
            break;

    return ret;
}

int mrp_perform_leave_request_on_all_attributes (struct mrp * mrp)
{
    struct mrp_attribute * mrp_attribute;
    struct mrp_attribute * next;

    list_for_each_entry_safe (mrp_attribute, next, &mrp->attributes, list)
    {
        mrp_perform_mad_leave_request (mrp,
            mrp_attribute_get_attribute_type (mrp_attribute),
            mrp_attribute_get_attribute_value (mrp_attribute),
            mrp_attribute_get_four_packed_type (mrp_attribute)
        );
    }

    return 0;
}

int mrp_start_jointimer (struct mrp * mrp)
{
    if ( ! timer_pending (&mrp->jointimer) )
    {
        mrp->jointimer.expires = jiffies + MRP_TIMER_VALUE_JOIN_TIME_JIFFIES;
        add_timer (&mrp->jointimer);
    }

    return 0;
}

int mrp_perform_jointimer_expire (struct mrp * mrp)
{
    struct mrp_state_machine * mrp_state_machine;
    struct mrp_state_machine * mrp_state_machine_next;
    int ret = 0;

    list_for_each_entry_safe (mrp_state_machine, mrp_state_machine_next, &mrp->request_opportunity_list, request_opportunity_list)
    {
        if ((mrp_state_machine == (struct mrp_state_machine *)mrp->mrp_leave_all))
            ret = mrp_state_machine_receive_protocol_event (mrp_state_machine, MRP_PROTO_EVENT_tx);
        else
            ret = mrp_state_machine_receive_protocol_event (mrp_state_machine, mrp_state_machine_get_current_state ((struct mrp_state_machine *)mrp->mrp_leave_all) == MRP_LEAVE_ALL_STATE_ACTIVE ? MRP_PROTO_EVENT_txLA : MRP_PROTO_EVENT_tx);
        list_del (&mrp_state_machine->request_opportunity_list);
    }

    return ret;
}

int mrp_perform_mad_join_request (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type, int new)
{
    struct mrp_attribute * mrp_attribute;

    if ( ! (mrp_attribute = mrp_add_and_return_attribute (mrp, mrp_attribute_type, attribute_value, four_packed_type)))
        return -ENOMEM;

    return new ? mrp_attribute_perform_new (mrp_attribute) : mrp_attribute_perform_join (mrp_attribute);
}

int mrp_perform_mad_leave_request (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type)
{
    struct mrp_attribute * mrp_attribute;

    if ( ! (mrp_attribute = mrp_get_attribute (mrp, mrp_attribute_type, attribute_value)) )
        return -EINVAL;

    return mrp_attribute_perform_lv (mrp_attribute);
}

int mrp_perform_mad_join_indication (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type, int new)
{
    return call_mrp_mad_join_indications(mrp, mrp_attribute_type, attribute_value, four_packed_type, new);
}

int mrp_perform_mad_leave_indication (struct mrp * mrp, int mrp_attribute_type, void * attribute_value, int four_packed_type)
{
    return call_mrp_mad_leave_indications(mrp, mrp_attribute_type, attribute_value, four_packed_type);
}

int mrp_perform_send_join (struct mrp * mrp, struct mrp_attribute * mrp_attribute)
{
    struct mrp_mrpdu * mrpdu;
    u8 attribute_event;
    int ret;

    if (mrp_attribute_is_registered (mrp_attribute))
        attribute_event = MRP_ATTRIBUTE_EVENT_JoinIn;
    else
        attribute_event  = MRP_ATTRIBUTE_EVENT_JoinMt;

    // MRP_PRINT_INFO ("mrp_perform_send_join\n");

    if ((mrpdu = mrp_mrpdu_create_and_init (mrp, 500, mrp->use_attribute_list_length)))
    {
        mrp_mrpdu_set_protocol_version (mrpdu, mrp->protocol_version);
        mrp_mrpdu_add_attribute_type (mrpdu, mrp_attribute_get_attribute_type (mrp_attribute));
        mrp_mrpdu_add_attribute_length (mrpdu, mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_vector_header (mrpdu, MRP_LEAVE_ALL_EVENT_NullLeaveAllEvent, 1);
        mrp_mrpdu_add_first_value (mrpdu, mrp_attribute_get_attribute_value (mrp_attribute), mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_three_packed_events (mrpdu, attribute_event, 0, 0);
        if (mrp->mrp_ops->mrp_does_need_four_packed_events (mrp_attribute_get_attribute_type (mrp_attribute)))
        {
            mrp_mrpdu_add_four_packed_events (mrpdu, mrp_attribute->four_packed_type, 0, 0, 0);
        }
        mrp_mrpdu_add_attribute_list_end_mark (mrpdu, MRP_MRPDU_EndMark);
        mrp_mrpdu_add_mrpdu_end_mark (mrpdu, MRP_MRPDU_EndMark);

        ret = mrp_port_send_mrpdu (mrp->mrp_port, mrpdu);

        mrp_mrpdu_destroy (&mrpdu);
        return ret;
    }

    return -ENOMEM;
}

int mrp_perform_send_new   (struct mrp * mrp, struct mrp_attribute * mrp_attribute)
{
    struct mrp_mrpdu * mrpdu;
    u8 attribute_event;
    int ret;

    attribute_event = MRP_ATTRIBUTE_EVENT_New;

    // MRP_PRINT_INFO ("mrp_perform_send_new\n");

    if ((mrpdu = mrp_mrpdu_create_and_init (mrp, 500, mrp->use_attribute_list_length)))
    {
        mrp_mrpdu_set_protocol_version (mrpdu, mrp->protocol_version);
        mrp_mrpdu_add_attribute_type (mrpdu, mrp_attribute_get_attribute_type (mrp_attribute));
        mrp_mrpdu_add_attribute_length (mrpdu, mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_vector_header (mrpdu, MRP_LEAVE_ALL_EVENT_NullLeaveAllEvent, 1);
        mrp_mrpdu_add_first_value (mrpdu, mrp_attribute_get_attribute_value (mrp_attribute), mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_three_packed_events (mrpdu, attribute_event, 0, 0);
        if (mrp->mrp_ops->mrp_does_need_four_packed_events (mrp_attribute_get_attribute_type (mrp_attribute)))
        {
            mrp_mrpdu_add_four_packed_events (mrpdu, mrp_attribute->four_packed_type, 0, 0, 0);
        }
        mrp_mrpdu_add_attribute_list_end_mark (mrpdu, MRP_MRPDU_EndMark);
        mrp_mrpdu_add_mrpdu_end_mark (mrpdu, MRP_MRPDU_EndMark);

        ret = mrp_port_send_mrpdu (mrp->mrp_port, mrpdu);

        mrp_mrpdu_destroy (&mrpdu);
        return ret;
    }

    return -ENOMEM;

}

int mrp_perform_send_leave (struct mrp * mrp, struct mrp_attribute * mrp_attribute)
{
    struct mrp_mrpdu * mrpdu;
    u8 attribute_event;
    int ret;

    attribute_event = MRP_ATTRIBUTE_EVENT_Lv;

    // MRP_PRINT_INFO ("mrp_perform_send_leave\n");

    if ((mrpdu = mrp_mrpdu_create_and_init (mrp, 500, mrp->use_attribute_list_length)))
    {
        mrp_mrpdu_set_protocol_version (mrpdu, mrp->protocol_version);
        mrp_mrpdu_add_attribute_type (mrpdu, mrp_attribute_get_attribute_type (mrp_attribute));
        mrp_mrpdu_add_attribute_length (mrpdu, mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_vector_header (mrpdu, MRP_LEAVE_ALL_EVENT_NullLeaveAllEvent, 1);
        mrp_mrpdu_add_first_value (mrpdu, mrp_attribute_get_attribute_value (mrp_attribute), mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_three_packed_events (mrpdu, attribute_event, 0, 0);
        if (mrp->mrp_ops->mrp_does_need_four_packed_events (mrp_attribute_get_attribute_type (mrp_attribute)))
        {
            mrp_mrpdu_add_four_packed_events (mrpdu, mrp_attribute->four_packed_type, 0, 0, 0);
        }
        mrp_mrpdu_add_attribute_list_end_mark (mrpdu, MRP_MRPDU_EndMark);
        mrp_mrpdu_add_mrpdu_end_mark (mrpdu, MRP_MRPDU_EndMark);

        ret = mrp_port_send_mrpdu (mrp->mrp_port, mrpdu);

        mrp_mrpdu_destroy (&mrpdu);
        return ret;
    }

    return -ENOMEM;
}

int mrp_perform_send (struct mrp * mrp, struct mrp_attribute * mrp_attribute)
{
    struct mrp_mrpdu * mrpdu;
    u8 attribute_event;
    int ret;

    if (mrp_attribute_is_registered (mrp_attribute))
        attribute_event = MRP_ATTRIBUTE_EVENT_In;
    else
        attribute_event = MRP_ATTRIBUTE_EVENT_Mt;

    // MRP_PRINT_INFO ("mrp_perform_send\n");

    if ((mrpdu = mrp_mrpdu_create_and_init (mrp, 500, mrp->use_attribute_list_length)))
    {
        mrp_mrpdu_set_protocol_version (mrpdu, mrp->protocol_version);
        mrp_mrpdu_add_attribute_type (mrpdu, mrp_attribute_get_attribute_type (mrp_attribute));
        mrp_mrpdu_add_attribute_length (mrpdu, mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_vector_header (mrpdu, MRP_LEAVE_ALL_EVENT_NullLeaveAllEvent, 1);
        mrp_mrpdu_add_first_value (mrpdu, mrp_attribute_get_attribute_value (mrp_attribute), mrp_attribute_get_attribute_value_length (mrp_attribute));
        mrp_mrpdu_add_three_packed_events (mrpdu, attribute_event, 0, 0);
        if (mrp->mrp_ops->mrp_does_need_four_packed_events (mrp_attribute_get_attribute_type (mrp_attribute)))
        {
            mrp_mrpdu_add_four_packed_events (mrpdu, mrp_attribute->four_packed_type, 0, 0, 0);
        }
        mrp_mrpdu_add_attribute_list_end_mark (mrpdu, MRP_MRPDU_EndMark);
        mrp_mrpdu_add_mrpdu_end_mark (mrpdu, MRP_MRPDU_EndMark);

        ret = mrp_port_send_mrpdu (mrp->mrp_port, mrpdu);

        mrp_mrpdu_destroy (&mrpdu);
        return ret;
    }

    return -ENOMEM;
}

int mrp_perform_send_leave_all (struct mrp * mrp)
{
    struct mrp_mrpdu * mrpdu;
    struct mrp_attribute * mrp_attribute;
    int ret = 0;

    // MRP_PRINT_INFO ("mrp_perform_send_leave_all\n");

    if ((ret = mrp_perform_receive_leave_all (mrp, 0)))
        return ret;

    list_for_each_entry (mrp_attribute, &mrp->attributes, list)
    {
        if ((mrpdu = mrp_mrpdu_create_and_init (mrp, 500, mrp->use_attribute_list_length)))
        {
            mrp_mrpdu_set_protocol_version (mrpdu, mrp->protocol_version);
            mrp_mrpdu_add_attribute_type (mrpdu, mrp_attribute_get_attribute_type (mrp_attribute));
            mrp_mrpdu_add_attribute_length (mrpdu, mrp_attribute_get_attribute_value_length (mrp_attribute));
            //mrp_mrpdu_add_attribute_list_length (mrpdu, 0);
            mrp_mrpdu_add_vector_header (mrpdu, MRP_LEAVE_ALL_EVENT_LeaveAll, 0);
            //mrp_mrpdu_add_first_value (mrpdu, mrp_attribute_get_attribute_value (mrp_attribute), mrp_attribute_get_attribute_value_length (mrp_attribute));
            //mrp_mrpdu_add_three_packed_events (mrpdu, attribute_event, 0, 0);
            mrp_mrpdu_add_attribute_list_end_mark (mrpdu, MRP_MRPDU_EndMark);

            mrp_mrpdu_add_mrpdu_end_mark (mrpdu, MRP_MRPDU_EndMark);

            ret = mrp_port_send_mrpdu (mrp->mrp_port, mrpdu);

            mrp_mrpdu_destroy (&mrpdu);
        }
        else
        {
            return -ENOMEM;
        }
    }

    return ret;
}

static int mrp_perform_receive_mrpdu_attribute_event (struct mrp * mrp, struct mrp_mrpdu * mrp_mrpdu, u8 attribute_type, u8 * first_value, u8 attribute_event, int four_packed_type, int attribute_event_number)
{
    struct mrp_attribute * mrp_attribute;
    u8 * first_value_copy;

    if ( ! (first_value_copy = kmalloc (mrp_mrpdu->last_attribute_length, MRP_GET_CONTEXT_ALLOC_FLAG)))
        return -ENOMEM;
    memcpy (first_value_copy, first_value, mrp_mrpdu->last_attribute_length);
    mrp->mrp_ops->mrp_increment_first_value (attribute_type, first_value_copy, attribute_event_number);

    if (! (mrp_attribute = mrp_add_and_return_attribute (mrp, attribute_type, first_value_copy, four_packed_type)))
    {
        kfree (first_value_copy);
        return -ENOMEM;
    }

    kfree (first_value_copy);

    switch (attribute_event)
    {
        case MRP_ATTRIBUTE_EVENT_New: 		return mrp_attribute_perform_receive_new 		(mrp_attribute);
        case MRP_ATTRIBUTE_EVENT_JoinIn: 	return mrp_attribute_perform_receive_join_in 	(mrp_attribute);
        case MRP_ATTRIBUTE_EVENT_In:		return mrp_attribute_perform_receive_in 		(mrp_attribute);
        case MRP_ATTRIBUTE_EVENT_JoinMt:	return mrp_attribute_perform_receive_join_mt 	(mrp_attribute);
        case MRP_ATTRIBUTE_EVENT_Mt:		return mrp_attribute_perform_receive_mt 		(mrp_attribute);
        case MRP_ATTRIBUTE_EVENT_Lv:		return mrp_attribute_perform_receive_lv 		(mrp_attribute);
        default: { MRP_PRINT_INFO ("%s\n", "mrp_perform_receive_mrpdu_attribute_event invalid attribute_event"); return -EINVAL; }
    }

    return 0;
}

int mrp_perform_receive_mrpdu (struct mrp * mrp, struct mrp_mrpdu * mrp_mrpdu)
{
    int ret;
    struct mrp_attribute * mrp_attribute;
    int num_attribute_events_left;
    int num_attribute_events_processed;
    int num_three_packed_events;
    int num_four_packed_events;

    u8 protocol_version;
    u8 attribute_type;
    u8 attribute_length;
    u16 attribute_list_length;
    u8 * first_value;
    u8 * three_packed_events;
    u8 * four_packed_events = NULL;
    u8 leave_all_event;
    u16 number_of_values;
    u8 attribute_event [3];
    u8 four_packed_type [4];
    u16 end_mark;
    u16 end_mark_peek;

    int attribute_event_index;
    int four_packed_type_index;
    int three_packed_events_index;
    int four_packed_events_index;

    memset (four_packed_type, 0, 4);

    // MRP_PRINT_INFO ("mrp_perform_receive_mrpdu\n");

    //reset_curr (mrp_mrpdu); mrp_mrpdu_print (mrp_mrpdu); return 0;

    reset_curr (mrp_mrpdu);

    if ((ret = mrp_mrpdu_get_protocol_version (mrp_mrpdu, &protocol_version)))
    {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_protocol_version FAILED"); return ret; }

    if (protocol_version > mrp->protocol_version)
    {	MRP_PRINT_INFO ("%s\n", "protocol_version FAILED"); return -EINVAL; }

    do
    {
        if ((ret = mrp_mrpdu_get_attribute_type (mrp_mrpdu, &attribute_type)))
        {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_attribute_type FAILED"); return ret; }

        if ((ret = mrp_mrpdu_get_attribute_length (mrp_mrpdu, &attribute_length)))
        {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_attribute_length FAILED"); return ret; }

        if (mrp->use_attribute_list_length)
        {
            if ((ret = mrp_mrpdu_get_attribute_list_length (mrp_mrpdu, &attribute_list_length)))
            {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_attribute_list_length FAILED"); return ret; }
        }
        else
            attribute_list_length = 0;

        if (mrp->mrp_ops->mrp_get_attribute_length (attribute_type) != attribute_length)
        {	MRP_PRINT_INFO ("%s\n", "mrp->mrp_ops->mrp_get_attribute_length (attribute_type) != attribute_length"); return -EINVAL; }

        do
        {
            if ((ret = mrp_mrpdu_get_vector_header (mrp_mrpdu, &leave_all_event, &number_of_values)))
            {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_vector_header FAILED"); return ret; }

            if (leave_all_event == MRP_LEAVE_ALL_EVENT_LeaveAll)
            {
                list_for_each_entry (mrp_attribute, &mrp->attributes, list)
                    if (mrp_attribute_get_attribute_type (mrp_attribute) == attribute_type)
                        mrp_attribute_perform_receive_leave_all (mrp_attribute);
            }

            if (number_of_values > 0)
            {
                num_attribute_events_left = number_of_values;
                num_attribute_events_processed = 0;

                if ( ! (first_value = kmalloc (mrp_mrpdu->last_attribute_length, MRP_GET_CONTEXT_ALLOC_FLAG)))
                {	MRP_PRINT_INFO ("%s\n", "first_value = kmalloc FAILED"); return -ENOMEM; }

                if ((ret = mrp_mrpdu_get_first_value (mrp_mrpdu, first_value, mrp_mrpdu->last_attribute_length)))
                {
                    MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_first_value FAILED");
                    kfree (first_value);
                    return ret;
                }

                num_three_packed_events = ((number_of_values / 3) + 1);

                if (!(three_packed_events = kmalloc (num_three_packed_events, MRP_GET_CONTEXT_ALLOC_FLAG)))
                { 	MRP_PRINT_INFO ("%s\n", "three_packed_events kmalloc FAILED"); return ret; }

                if ((ret = mrp_mrpdu_get_bytes (mrp_mrpdu, three_packed_events, num_three_packed_events)))
                {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_bytes three_packed_events FAILED"); return ret; }

                if (mrp->mrp_ops->mrp_does_need_four_packed_events (attribute_type))
                {
                    num_four_packed_events  = ((number_of_values / 4) + 1);

                    if (!(four_packed_events = kmalloc (num_four_packed_events, MRP_GET_CONTEXT_ALLOC_FLAG)))
                    { 	MRP_PRINT_INFO ("%s\n", "four_packed_events kmalloc FAILED"); return ret; }

                    if ((ret = mrp_mrpdu_get_bytes (mrp_mrpdu, four_packed_events, num_four_packed_events)))
                    {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_bytes four_packed_events FAILED"); return ret; }
                }

                attribute_event_index  = 0;
                four_packed_type_index = 0;
                three_packed_events_index = 0;
                four_packed_events_index  = 0;

                do
                {
                    if (attribute_event_index % 3 == 0)
                    {
                        mrp_mrpdu_get_three_packed_events_from_byte (three_packed_events[three_packed_events_index++], attribute_event, attribute_event + 1, attribute_event + 2);
                        attribute_event_index  = 0;
                    }

                    if (mrp->mrp_ops->mrp_does_need_four_packed_events (attribute_type))
                    {
                        if (four_packed_type_index % 4 == 0)
                        {
                            mrp_mrpdu_get_four_packed_events_from_byte (four_packed_events[four_packed_events_index++], four_packed_type, four_packed_type + 1, four_packed_type + 2, four_packed_type + 3);
                            four_packed_events_index = 0;
                        }
                    }

                    if ((ret = mrp_perform_receive_mrpdu_attribute_event (mrp, mrp_mrpdu, attribute_type, first_value, attribute_event [attribute_event_index++], four_packed_type [four_packed_type_index++], num_attribute_events_processed++)))
                    {
                        MRP_PRINT_INFO ("%s\n", "mrp_perform_receive_mrpdu_attribute_event FAILED"); return ret;
                    }

                } while (--num_attribute_events_left > 0) ;

                kfree (three_packed_events);
                kfree (four_packed_events);
                kfree (first_value);
            }

            if ((ret = mrp_mrpdu_peek (mrp_mrpdu, &end_mark_peek, MRP_MRPDU_END_MARK_SIZE)))
            { 	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_peek FAILED"); return ret; }

        } while (end_mark_peek != MRP_MRPDU_EndMark) ;

        if ((ret = mrp_mrpdu_get_end_mark (mrp_mrpdu, &end_mark)))
        { 	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_end_mark FAILED"); return ret; }

        if ((ret = mrp_mrpdu_peek (mrp_mrpdu, &end_mark_peek, MRP_MRPDU_END_MARK_SIZE)))
        {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_peek FAILED"); return ret; }

    } while (end_mark_peek != MRP_MRPDU_EndMark) ;

    if ((ret = mrp_mrpdu_get_end_mark (mrp_mrpdu, &end_mark)))
    {	MRP_PRINT_INFO ("%s\n", "mrp_mrpdu_get_end_mark FAILED"); return ret; }

    return 0;
}

static int mrp_init_module (void)
{
    MRP_PRINT_INFO ("%s\n", "MRP Initialising");
    
    mrp_message_processing_thread_init_lock();

    MRP_PRINT_INFO ("%s\n", "MRP Initialisation complete");

    return 0;
}

static void mrp_cleanup_module (void)
{
    MRP_PRINT_INFO ("%s\n", "MRP Clean up");

    MRP_PRINT_INFO ("%s\n", "MRP Clean up complete");
}

module_init (mrp_init_module);
module_exit (mrp_cleanup_module);
